﻿using FluentValidation.Results;
using log4net;
using PagedList;
using QAS.Nekorama.Common;
using QAS.Nekorama.Filters;
using QAS.Nekorama.Models;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Text.RegularExpressions;
using System.Web.Mvc;
using WebMatrix.WebData;
using Resources;

namespace QAS.Nekorama.Controllers
{
    /// <summary>
    /// 質問コントローラー
    /// ビュー含有: Index.cshtml, Details.cshtml, Create.cshtml, Edit.cshtml 
    /// アクション含有: Index, Details, Answer, Delete, DeleteAnswer, Report, Vote, ReportAnswer, Create, Edit, NewQuestion, Edit. 
    /// IsNotEmpty, PostCount, UpdateQuestion, InsertBounty, InsertNewTag, InsertQuestion, GetTags,
    /// Comment, EditAnswer, EditPartial, AcceptAnswer, SetBounty, UpdateViewCount  
    /// </summary>
    [InitializeSimpleMembership]
    [HandleError]
    public class QuestionController : Controller
    {
        private readonly Entities _db = new Entities();
        private readonly ILog _log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);

        /// <summary>
        /// Indexビューからパラメーターをもらい、モデルに相応なデータを取り出す
        /// </summary>
        /// <param name="sortOrder">フィルタストリング</param>
        /// <param name="page">現在なページの番</param>
        /// <param name="searchString">検索キーワード</param>
        /// <param name="currentFilter">現在なフィルタのためのストリング</param>
        /// <returns>Indexビュー</returns>
        public ActionResult Index(string sortOrder, int? page, string searchString, string currentFilter)
        {
            _log.Debug("Start action " + MethodBase.GetCurrentMethod().Name);
            try
            {
                _log.Debug(string.Format(" sortorder:{0}, searchstring:{1},  currentfilter:{2}, page:{3}", sortOrder,
                    searchString, currentFilter, page));
                ViewBag.NewestSortParm = "Newest";
                ViewBag.FrequentSortParm = "Frequent";
                ViewBag.VotesSortParm = "Vote";
                ViewBag.FeaturedSortParm = "Featured";
                ViewBag.UnansweredSortParm = "Unanswered";
                ViewBag.SearchStringParm = searchString;
                User userInfo = Helper.GetUserInfo(WebSecurity.CurrentUserId);
                ViewBag.CurrentUserInfo = userInfo;
                //ページング中に現在なフィルタにフィルタストリングをします。
                ViewBag.CurrentSort = sortOrder;
                //ページング中に現在なフィルタに検索キーワードをします。そして、ページが再表示される時テキストボックスに検索キーワードを戻します。
                if (!String.IsNullOrWhiteSpace(searchString))
                {
                    //新しい検索キーワードがビューから送られます。他の結果ページを表示するためページを１に再設定します。
                    page = 1;
                }
                else
                {
                    //ページング.
                    searchString = currentFilter;
                }
                ViewBag.CurrentFilter = searchString;
                IQueryable<Question> questionList = from q in _db.Questions
                                                    where q.IsDeleted != true && q.IsSpam != true
                                                    select q;
                //検索機能を始めます。
                if (!String.IsNullOrWhiteSpace(searchString))
                {
                    _log.Debug(string.Format(" Begin search."));
                    searchString = searchString.Trim();
                    //検索キーワードにタグが入っています。
                    if (searchString.StartsWith("#"))
                    {
                        //′#'字を削除する
                        searchString = searchString.Substring(1);
                        string tagKey = "";                        
                        if (searchString.Contains(':'))
                        {
                            _log.Debug(string.Format(" Search full text with specific tag {0}", searchString));
                            //タグを抽出する
                            tagKey = searchString.Split(':')[0];
                            //タグ名の長さからキーワードを抽出する
                            string textKey = searchString.Substring((tagKey.Length + 1)).Trim();
                            if (!String.IsNullOrEmpty(textKey))
                            {
                                //検索キーワードに引用符が入っていませんから全文検索です。
                                if (!IsContainQuoteMark(textKey))
                                {
                                    questionList = from q in _db.Questions
                                                   join fts in _db.QuestionSearch(textKey)
                                                   on q.QuestionID equals fts.KEY
                                                   select q;
                                }
                                //検索キーワードに引用符が入っていますから正確検索です。
                                else
                                {
                                    //始めと終わりに引用符を削除します。
                                    textKey = textKey.Substring(1, (textKey.Length - 2));
                                    questionList = questionList.Where(q => q.TagName1.Contains(textKey)
                                                               || q.TagName2.Contains(textKey)
                                                               || q.TagName3.Contains(textKey)
                                                               || q.TagName4.Contains(textKey)
                                                               || q.TagName5.Contains(textKey)
                                                               || q.Title.Contains(textKey)
                                                               || q.Content.Contains(textKey));
                                }
                            }
                        }
                        else
                        {
                            _log.Debug(string.Format(" Search question by tag {0}", searchString));                          
                            tagKey = searchString;
                        }
                        questionList = questionList.Where(q => q.TagName1.Equals(tagKey)
                                                               || q.TagName2.Equals(tagKey)
                                                               || q.TagName3.Equals(tagKey)
                                                               || q.TagName4.Equals(tagKey)
                                                               || q.TagName5.Equals(tagKey));
                    }
                    //全文検索
                    else
                    {
                        _log.Debug(string.Format(" Full text search {0}", searchString));
                        //検索キーワードに引用符が入っていませんから全文検索です。
                        if (!IsContainQuoteMark(searchString))
                        {
                            questionList = from q in _db.Questions
                                           join fts in _db.QuestionSearch(searchString)
                                           on q.QuestionID equals fts.KEY
                                           select q;
                        }
                        //検索キーワードに引用符が入っていますから正確検索です。
                        else
                        {
                            //始めと終わりに引用符を削除します。
                            searchString = searchString.Substring(1, (searchString.Length - 2));
                            questionList = questionList.Where(q => q.TagName1.Contains(searchString)
                                                              || q.TagName2.Contains(searchString)
                                                              || q.TagName3.Contains(searchString)
                                                              || q.TagName4.Contains(searchString)
                                                              || q.TagName5.Contains(searchString)
                                                              || q.Title.Contains(searchString)
                                                              || q.Content.Contains(searchString));
                        }
                    }
                }
                switch (sortOrder)
                {
                    case "Newest":
                        questionList = questionList.OrderByDescending(q => q.CreatedDate);
                        break;

                    case "Frequent":
                        questionList = questionList.OrderByDescending(q => q.ViewCount);
                        break;

                    case "Vote":
                        questionList = questionList.OrderByDescending(q => q.VoteCount);
                        break;

                    case "Featured":
                        questionList = questionList.Where(q => q.BountyID != null && q.Bounty.AnswerID == null)
                                        .OrderByDescending(q => q.CreatedDate);
                        break;

                    case "Unanswered":
                        questionList = questionList.Where(
                            q => q.Answers.Count(a => a.IsDeleted != true && a.IsSpam != true) == 0)
                                        .OrderByDescending(q => q.CreatedDate);
                        break;

                    default:
                        questionList = questionList.OrderByDescending(q => q.CreatedDate);
                        break;
                }
                const int pageSize = 10;
                int pageNumber = (page ?? 1);
                ViewBag.resultno = questionList.Count();
                _log.Debug("End action " + MethodBase.GetCurrentMethod().Name);
                return View(questionList.ToPagedList(pageNumber, pageSize));
            }
            catch (ArgumentNullException exception)
            {
                _log.Error(
                    string.Format("An {0} exception has occured at {1} action. ", exception.GetType(),
                        MethodBase.GetCurrentMethod().Name) + exception.Message, exception);
                return View("Error", new HandleErrorInfo(exception, "Question", "Index"));
            }
            catch (InvalidOperationException exception)
            {
                _log.Error(
                    string.Format("An {0} exception has occured at {1} action. ", exception.GetType(),
                        MethodBase.GetCurrentMethod().Name) + exception.Message, exception);
                return View("Error", new HandleErrorInfo(exception, "Question", "Index"));
            }
            catch (ArgumentOutOfRangeException exception)
            {
                _log.Error(
                    string.Format("An {0} exception has occured at {1} action. ", exception.GetType(),
                        MethodBase.GetCurrentMethod().Name) + exception.Message, exception);
                return View("Error", new HandleErrorInfo(exception, "Question", "Index"));
            }
            catch (OverflowException exception)
            {
                _log.Error(
                    string.Format("An {0} exception has occured at {1} action. ", exception.GetType(),
                        MethodBase.GetCurrentMethod().Name) + exception.Message, exception);
                return View("Error", new HandleErrorInfo(exception, "Question", "Index"));
            }
            catch (Exception exception)
            {
                _log.Fatal(
                    string.Format("A fatal exception has occured at {0} action ", MethodBase.GetCurrentMethod().Name) +
                    exception.Message, exception);
                throw;
            }
        }

        /// <summary>
        /// 検索キーワードに引用符が入っているかどうか確認します。
        /// </summary>
        /// <param name="searchString">検索キーワード</param>
        /// <returns></returns>
        public bool IsContainQuoteMark(string searchString)
        {
            if ((searchString.StartsWith("\"") || searchString.StartsWith("”"))
                && (searchString.EndsWith("\"") || searchString.EndsWith("”")))
                return true;
            return false;
        }

        /// <summary>
        /// 質問の詳細ページがアクセスされるたび、質問のビュー数を更新する
        /// </summary>
        /// <param name="id">質問のID</param>
        public void UpdateViewCount(int id)
        {
            _log.Debug("Start action " + MethodBase.GetCurrentMethod().Name);
            try
            {
                Question question = _db.Questions.Find(id);
                if (question != null)
            {
                 if (Helper.IsInString(WebSecurity.CurrentUserId, question.VisitedTodayUserList)) return;
            
                question.VisitedTodayUserList += WebSecurity.CurrentUserId + ",";

                if (question.ViewCount == null)
                    question.ViewCount = 1;
                else
                {
                    question.ViewCount++;
                }
            }
                _db.SaveChanges();
                _log.Debug("End action " + MethodBase.GetCurrentMethod().Name);
            }

            catch (InvalidOperationException exception)
            {
                _log.Error(
                    string.Format("An {0} exception has occured at {1} action. ", exception.GetType(),
                        MethodBase.GetCurrentMethod().Name) + exception.Message, exception);
            }
            catch (Exception exception)
            {
                _log.Fatal(
                    string.Format("A fatal exception has occured at {0} action ", MethodBase.GetCurrentMethod().Name) +
                    exception.Message, exception);
                throw;
            }
        }

        /// <summary>
        /// 質問の情報と質問の回答と論評を表示します。
        /// </summary>
        /// <param name="id">質問のID</param>
        /// <returns>Detailsビュー</returns>
        [HttpGet]
        //[OutputCache(Duration = 60, VaryByParam = "id"),]
        public ActionResult Details(int id)
        {
            _log.Debug("Start action " + MethodBase.GetCurrentMethod().Name + ",question id =" + id);

            var question = new QuestionDetailModel();
            try
            {
                User userInfo = Helper.GetUserInfo(WebSecurity.CurrentUserId);
                ViewBag.CurrentUserInfo = userInfo;
                _log.Debug(string.Format("Filling data to the model..."));
                IEnumerable<Answer> answer =
                    _db.Answers.Where(a => a.QuestionID == id)
                        .OrderByDescending(a => a.IsAccepted)
                        .ThenByDescending(a => a.VoteCount);
                IEnumerable<Comment> comment = (from c in _db.Comments.AsEnumerable()
                                                where (c.QuestionID == id)
                                                select c);
                _log.Debug(string.Format("Getting validation failures if any..."));
                question.Question = _db.Questions.SingleOrDefault(q => q.QuestionID == id);
                if (question.Question == null)
                {
                    return View("Error",
                        new HandleErrorInfo(new Exception(Messages.QUESTIONID_DOES_NOT_EXIST), "Question", "Details"));
                }
                question.Failures = (ICollection<ValidationFailure>) TempData["ValidationFailures"];
                question.Bounty = _db.Bounties.SingleOrDefault(b => b.QuestionID == id);
                question.Answers = answer.ToList();
                question.Comments = comment.ToList();
            }
            catch (ArgumentNullException exception)
            {
                _log.Error(
                    string.Format("An {0} exception has occured at {1} action. ", exception.GetType(),
                        MethodBase.GetCurrentMethod().Name) + exception.Message, exception);
                return View("Error", new HandleErrorInfo(exception, "Question", "Details"));
            }
            catch (InvalidOperationException exception)
            {
                _log.Error(
                    string.Format("An {0} exception has occured at {1} action. ", exception.GetType(),
                        MethodBase.GetCurrentMethod().Name) + exception.Message, exception);
                return View("Error", new HandleErrorInfo(exception, "Question", "Details"));
            }
            catch (Exception exception)
            {
                _log.Fatal(
                    string.Format("A fatal exception has occured at {0} action ", MethodBase.GetCurrentMethod().Name) +
                    exception.Message, exception);
                throw;
            } 
            UpdateViewCount(id);
            _log.Debug("End action " + MethodBase.GetCurrentMethod().Name);
            return View(question);
        }

        /// <summary>
        ///　新しい回答を投稿します。
        /// </summary>
        /// <param name="answer">回答モデル</param>
        /// <returns>質問ビュー</returns>
        [HttpPost]
        public ActionResult Answer(Answer answer)
        {
            _log.Debug("Start action " + MethodBase.GetCurrentMethod().Name);
            try
            {
                _log.Debug(string.Format("Validating answer content: \"{0}\"", answer.Content));
                var validator = new CustomValidators.AnswerValidator();
                ValidationResult result = validator.Validate(answer);
                TempData.Add("ValidationFailures", result.Errors);
                if (!result.IsValid)
                {
                    _log.Error(string.Format("Validation for answer content failed."));

                    return null; 
                }
                _log.Debug(string.Format("Checking for privilege to post 5 answers perday."));
                IQueryable<Answer> answerList = _db.Answers.Where(a => a.CreatedUser == answer.CreatedUser);
                int count = Enumerable.Count(answerList, a => a.CreatedDate.Date == DateTime.Today.Date);
                if (_db.Users.Find(answer.CreatedUser).Reputation < (int) Constants.Privileges.Veteran && count >= 5)
                {
                    _log.Error(
                        string.Format("An error has occured at {0} action. ", MethodBase.GetCurrentMethod().Name) +
                        ": user do not have enough reputation or already submitted 5 posts today.");
                    result.Errors.Add(new ValidationFailure("Number", Messages.NEW_USER_RESTRICTION));
                    return null;
                }
                _db.Answers.Add(answer);
                _db.SaveChanges();
                //
                _log.Debug(string.Format("End Action {0}", MethodBase.GetCurrentMethod().Name));
                return RedirectToAction("Details", new {id = answer.QuestionID});
            }
            catch (ArgumentNullException exception)
            {
                _log.Error(
                    string.Format("An {0} exception has occured at {1} action. ", exception.GetType(),
                        MethodBase.GetCurrentMethod().Name) + exception.Message, exception);
                return View("Error", new HandleErrorInfo(exception, "Question", "Answer"));
            }
            catch (InvalidOperationException exception)
            {
                _log.Error(
                    string.Format("An {0} exception has occured at {1} action. ", exception.GetType(),
                        MethodBase.GetCurrentMethod().Name) + exception.Message, exception);
                return View("Error", new HandleErrorInfo(exception, "Question", "Answer"));
            }
            catch (OverflowException exception)
            {
                _log.Error(
                    string.Format("An {0} exception has occured at {1} action. ", exception.GetType(),
                        MethodBase.GetCurrentMethod().Name) + exception.Message, exception);
                return View("Error", new HandleErrorInfo(exception, "Question", "Answer"));
            }
            catch (Exception exception)
            {
                _log.Fatal(
                    string.Format("A fatal exception has occured at {0} action ", MethodBase.GetCurrentMethod().Name) +
                    exception.Message, exception);
                throw;
            }
        }

        /// <summary>
        /// 現在な質問を削除します。
        /// </summary>
        /// <param name="userId">この作用を実現したユーザー</param>
        /// <param name="questionId">質問のID</param>
        /// <returns>質問ビュー</returns>
        [HttpPost]
        public ActionResult Delete(int userId, int questionId)
        {
            _log.Debug("Start action " + MethodBase.GetCurrentMethod().Name +
                       string.Format(". From userid {0} and questionid {1}", userId, questionId));
            try
            {
               
                var question = _db.Questions.Find(questionId);
                if (question == null)
                {
                    return View("Error",
                        new HandleErrorInfo(new Exception(Messages.QUESTIONID_DOES_NOT_EXIST), "Question", "Details"));
                }
                if (Helper.GetUserInfo(userId).Reputation < (int)Constants.Privileges.Moderator || Helper.GetUserInfo(userId).BanDuration>0)
                {
                    var result = new ValidationResult();
                    result.Errors.Add(new ValidationFailure("Reps", Messages.NOT_ENOUGH_PERMISSION));
                    TempData.Add("ValidationFailures", result.Errors);
                    return null;
                }

                question.IsDeleted = true;
                foreach (var ans in _db.Answers.Where(a=>a.QuestionID==question.QuestionID))
                {
                    ans.IsDeleted = true;
                }
                Helper.ChangeReputation(userId, (int) Constants.Reputation.DeleteQuestion);

                _db.SaveChanges();
                _log.Debug("End action " + MethodBase.GetCurrentMethod().Name);
                return RedirectToAction("Index");
            }
            catch (InvalidOperationException exception)
            {
                _log.Error(
                    string.Format("An {0} exception has occured at {1} action. ", exception.GetType(),
                        MethodBase.GetCurrentMethod().Name) + exception.Message, exception);
                return View("Error", new HandleErrorInfo(exception, "Question", "Delete"));
            }
            catch (Exception exception)
            {
                _log.Fatal(
                    string.Format("A fatal exception has occured at {0} action ", MethodBase.GetCurrentMethod().Name) +
                    exception.Message, exception);
                throw;
            }
        }

        /// <summary>
        /// 現在な質問の回答を削除します。
        /// </summary>
        /// <param name="userId">この作用を実現したユーザー</param>
        /// <param name="answerId">回答のID</param>
        /// <returns>Detailsビュー</returns>
        [HttpPost]
        public ActionResult DeleteAnswer(int userId, int answerId)
        {
            _log.Debug("Start action " + MethodBase.GetCurrentMethod().Name +
                       string.Format(". From userid {0} and answerid {1}", userId, answerId));

            try
            {
               
                Answer answer = _db.Answers.Find(answerId);
                
                _log.Debug(string.Format("Deduct reputation. Set answerid {0} to Deleted status", answerId));
                if (answer.IsAccepted == true)
                {
                    _log.Error(
                        string.Format("An error has occured at {0} action. ", MethodBase.GetCurrentMethod().Name) +
                        "Cannot delete an accepted answer.");

                    return View("Error",
                        new HandleErrorInfo(new Exception(Messages.ACCEPTED_ANSWER_DELETE), "Question", "DeleteAnswer"));
                }
                if (Helper.GetUserInfo(userId).Reputation < (int)Constants.Privileges.Moderator || Helper.GetUserInfo(userId).BanDuration > 0)
                {
                    var result = new ValidationResult();
                    result.Errors.Add(new ValidationFailure("Reps", Messages.NOT_ENOUGH_PERMISSION));
                    TempData.Add("ValidationFailures", result.Errors);
                    return null;
                }
                int questionId = answer.QuestionID;
                answer.IsDeleted = true;
                Helper.ChangeReputation(userId, (int) Constants.Reputation.DeleteAnswer);

                _db.SaveChanges();
                _log.Debug("End action " + MethodBase.GetCurrentMethod().Name);

                return RedirectToAction("Index", "Question", new {id = questionId});
            }
            catch (InvalidOperationException exception)
            {
                _log.Error(
                    string.Format("An {0} exception has occured at {1} action. ", exception.GetType(),
                        MethodBase.GetCurrentMethod().Name) + exception.Message, exception);
                return View("Error", new HandleErrorInfo(exception, "Question", "DeleteAnswer"));
            }
            catch (Exception exception)
            {
                _log.Fatal(
                    string.Format("A fatal exception has occured at {0} action ", MethodBase.GetCurrentMethod().Name) +
                    exception.Message, exception);
                throw;
            }
        }

        /// <summary>
        /// 不適当な質問を報告します
        /// </summary>
        /// <param name="reportModel">Reportモデル</param>
        /// <returns>Indexビュー</returns>
        [HttpPost]
        public ActionResult Report(ReportModel reportModel)
        {
            _log.Debug("Start action " + MethodBase.GetCurrentMethod().Name +
                       string.Format(". From userid {0} and questionid {1}", reportModel.ReporterID,
                           reportModel.TargetID));

            try
            {
                Question question = _db.Questions.Find(reportModel.TargetID);
                if (question.SpamCount == null)
                {
                    _log.Debug(
                        string.Format(
                            "Question has not been reported before. Set spam count = 1 and add curent user to the reporter list. "));

                    question.SpamCount = 1;
                    question.SpamMarkUserList += reportModel.ReporterID + ",";
                    Helper.ChangeReputation(reportModel.ReporterID, (int) Constants.Reputation.ReportPost);
                }
                else
                {
                    _log.Debug(string.Format("Increment spam count. Add user to reporter list."));
                    question.SpamCount++;
                    question.SpamMarkUserList += reportModel.ReporterID + ",";
                    Helper.ChangeReputation(reportModel.ReporterID, (int) Constants.Reputation.ReportPost);
                    if (question.SpamCount >= Constants.SPAM_THRESHOLD)
                    {
                        _log.Debug(
                            string.Format(
                                "Question has been reported 5 times. Mark as spam. Deduct 100 reps from question creator."));
                        question.IsSpam = true;
                        foreach (var ans in _db.Answers.Where(a => a.QuestionID == question.QuestionID))
                        {
                            ans.IsSpam = true;
                        }
                        Helper.ChangeReputation(question.CreatedUser, (int) Constants.Reputation.PostGetMarkedAsSpam);
                    }
                }
                if (Helper.GetUserInfo(reportModel.ReporterID).Reputation < (int) Constants.Privileges.Report)
                {
                    var result = new ValidationResult();
                    result.Errors.Add(new ValidationFailure("Reps", Messages.NOT_ENOUGH_PERMISSION));
                    TempData.Add("ValidationFailures", result.Errors);
                    return null;
                }
                _db.SaveChanges();
                _log.Debug("End action " + MethodBase.GetCurrentMethod().Name);

                return null;
            }
            catch (InvalidOperationException exception)
            {
                _log.Error(
                    string.Format("An {0} exception has occured at {1} action. ", exception.GetType(),
                        MethodBase.GetCurrentMethod().Name) + exception.Message, exception);
                return View("Error", new HandleErrorInfo(exception, "Question", "Report"));
            }
            catch (Exception exception)
            {
                _log.Fatal(
                    string.Format("A fatal exception has occured at {0} action ", MethodBase.GetCurrentMethod().Name) +
                    exception.Message, exception);
                throw;
            }
        }

        /// <summary>
        /// 不適当な回答を報告します。
        /// </summary>
        /// <param name="reportModel">Reportモデル</param>
        /// <returns>Detailsビュー</returns>
        [HttpPost]
        public ActionResult ReportAnswer(ReportModel reportModel)
        {
            _log.Debug("Start action " + MethodBase.GetCurrentMethod().Name +
                       string.Format(". From userid {0} and questionid {1}", reportModel.ReporterID,
                           reportModel.TargetID));

            try
            {
                Answer answer = _db.Answers.Find(reportModel.TargetID);
                if (answer.SpamCount == null)
                {
                    _log.Debug(
                        string.Format("Answer hasnt been reported before. Set spamcount = 1. Add user to reporter list."));
                    answer.SpamCount = 1;
                    answer.SpamMarkUserList += reportModel.ReporterID + ",";
                    Helper.ChangeReputation(reportModel.ReporterID, (int) Constants.Reputation.ReportPost);
                }
                else
                {
                    _log.Debug(string.Format("Increment spam count. Add user to reporter list."));
                    answer.SpamCount++;
                    answer.SpamMarkUserList += reportModel.ReporterID + ",";
                    Helper.ChangeReputation(reportModel.ReporterID, (int) Constants.Reputation.ReportPost);
                    if (answer.SpamCount >= Constants.SPAM_THRESHOLD)
                    {
                        _log.Debug(
                            string.Format(
                                "Answer has been reported 5 times. Mark as spam.  Deduct 100 reps from question creator."));
                        answer.IsSpam = true;
                        Helper.ChangeReputation(answer.CreatedUser, (int) Constants.Reputation.PostGetMarkedAsSpam);
                    }
                }
                if (Helper.GetUserInfo(reportModel.ReporterID).Reputation < (int) Constants.Privileges.Report)
                {
                    var result = new ValidationResult();
                    result.Errors.Add(new ValidationFailure("Reps", Messages.NOT_ENOUGH_PERMISSION));
                    TempData.Add("ValidationFailures", result.Errors);
                    return null;
                }
                _db.SaveChanges();
                _log.Debug("End action " + MethodBase.GetCurrentMethod().Name);
                return RedirectToAction("Details", new {id = answer.QuestionID});
            }
            catch (InvalidOperationException exception)
            {
                _log.Error(
                    string.Format("An {0} exception has occured at {1} action. ", exception.GetType(),
                        MethodBase.GetCurrentMethod().Name) + exception.Message, exception);
                return View("Error", new HandleErrorInfo(exception, "Question", "ReportAnswer"));
            }
            catch (Exception exception)
            {
                _log.Fatal(
                    string.Format("A fatal exception has occured at {0} action ", MethodBase.GetCurrentMethod().Name) +
                    exception.Message, exception);
                throw;
            }
        }

        /// <summary>
        /// 質問と回答を投票します。
        /// </summary>
        /// <param name="vote">Voteモデル</param>
        /// <returns>Detailsビュー</returns>
        [HttpPost]
        public ActionResult Vote(VoteModel vote)
        {
            _log.Debug("Start action " + MethodBase.GetCurrentMethod().Name +
                       string.Format(". From userid {0}", vote.TargetID));

            try
            {
                if (vote.IsQuestion)
                {
                    _log.Debug(string.Format("Vote questionid = {0}", vote.TargetID));
                    if (vote.IsPositive)
                    {
                        _log.Debug(string.Format("Vote UP questionid = {0}", vote.TargetID));
                        Question question = _db.Questions.SingleOrDefault(q => q.QuestionID == vote.TargetID);

                        if (question != null && !Helper.IsInString(vote.Voter, question.VoteUpUserList))
                        {
                            _log.Debug(
                                string.Format(
                                    "Userid {0} has not vote this questionid {1} yet. Add user to the voter list.",
                                    vote.Voter, vote.TargetID));
                            question.VoteUpUserList += vote.Voter + ",";
                            question.VoteCount += 1;
                            Helper.ChangeReputation(question.CreatedUser, (int) Constants.Reputation.QuestionVotedUp);
                        }
                        else
                        {
                            //投票禁止
                            _log.Error(
                                string.Format("An error has occured at {0} action. ", MethodBase.GetCurrentMethod().Name) +
                                " This user already voted this post.");

                            return View("Error",
                                new HandleErrorInfo(new Exception(Messages.QUESTION_ALREADY_VOTED), "Question", "Vote"));
                        }
                        if (Helper.GetUserInfo(vote.Voter).Reputation < (int) Constants.Privileges.VoteUp)
                        {
                            var result = new ValidationResult();
                            result.Errors.Add(new ValidationFailure("Reps", Messages.NOT_ENOUGH_PERMISSION));
                            TempData.Add("ValidationFailures", result.Errors);
                            return null;
                        }
                        _db.SaveChanges();
                        _log.Debug("End action " + MethodBase.GetCurrentMethod().Name);
                        return RedirectToAction("Details", new {id = question.QuestionID});
                    }
                    else
                    {
                        _log.Debug(string.Format("Vote DOWN questionid = {0}", vote.TargetID));
                        Question question = _db.Questions.SingleOrDefault(q => q.QuestionID == vote.TargetID);

                        if (question != null && !Helper.IsInString(vote.Voter, question.VoteDownUserList))
                        {
                            _log.Debug(
                                string.Format(
                                    "Userid {0} hasnt vote this questionid {1} yet. Add user to the voter list.",
                                    vote.Voter, vote.TargetID));

                            question.VoteDownUserList += vote.Voter + ",";
                            question.VoteCount -= 1;
                            Helper.ChangeReputation(question.CreatedUser, (int) Constants.Reputation.QuestionVotedDown);
                            Helper.ChangeReputation(vote.Voter, (int) Constants.Reputation.VoteDownQuestion);
                        }
                        else
                        {
                            //投票禁止
                            _log.Error(
                                string.Format("An error has occured at {0} action. ", MethodBase.GetCurrentMethod().Name) +
                                " This user already voted this post.");

                            return View("Error",
                                new HandleErrorInfo(new Exception(Messages.QUESTION_ALREADY_VOTED), "Question", "Vote"));
                        }
                        if (Helper.GetUserInfo(vote.Voter).Reputation < (int) Constants.Privileges.VoteDown)
                        {
                            var result = new ValidationResult();
                            result.Errors.Add(new ValidationFailure("Reps", Messages.NOT_ENOUGH_PERMISSION));
                            TempData.Add("ValidationFailures", result.Errors);
                            return null;
                        }
                        _db.SaveChanges();
                        _log.Debug("End action " + MethodBase.GetCurrentMethod().Name);
                        return RedirectToAction("Details", new {id = question.QuestionID});
                    }
                }
                _log.Debug(string.Format("Vote answerid = {0}", vote.TargetID));
                if (vote.IsPositive)
                {
                    _log.Debug(string.Format("Vote UP answerid = {0}", vote.TargetID));
                    Answer answer = _db.Answers.SingleOrDefault(a => a.AnswerID == vote.TargetID);
                    if (answer != null && !Helper.IsInString(vote.Voter, answer.VoteUpUserList))
                    {
                        _log.Debug(
                            string.Format("Userid {0} hasnt vote this answerid {1} yet. Add user to the voter list.",
                                vote.Voter, vote.TargetID));
                        answer.VoteUpUserList += vote.Voter + ",";
                        answer.VoteCount += 1;
                        Helper.ChangeReputation(answer.CreatedUser, (int) Constants.Reputation.AnswerVotedUp);
                    }
                    else
                    {
                        //投票禁止
                        _log.Error(
                            string.Format("An error has occured at {0} action. ", MethodBase.GetCurrentMethod().Name) +
                            " This user already voted this post.");

                        return View("Error",
                            new HandleErrorInfo(new Exception(Messages.ANSWER_ALREADY_VOTED), "Question", "Vote"));
                    }
                    if (Helper.GetUserInfo(vote.Voter).Reputation < (int) Constants.Privileges.VoteUp)
                    {
                        var result = new ValidationResult();
                        result.Errors.Add(new ValidationFailure("Reps", Messages.NOT_ENOUGH_PERMISSION));
                        TempData.Add("ValidationFailures", result.Errors);
                        return null;
                    }
                    _db.SaveChanges();
                    _log.Debug("End action " + MethodBase.GetCurrentMethod().Name);
                    return RedirectToAction("Details", new {id = answer.QuestionID});
                }
                else
                {
                    Answer answer = _db.Answers.SingleOrDefault(a => a.AnswerID == vote.TargetID);
                    if (answer != null && !Helper.IsInString(vote.Voter, answer.VoteDownUserList))
                    {
                        _log.Debug(
                            string.Format("Userid {0} hasnt vote this answerid {1} yet. Add user to the voter list.",
                                vote.Voter, vote.TargetID));

                        answer.VoteDownUserList += vote.Voter + ",";
                        answer.VoteCount -= 1;
                        Helper.ChangeReputation(answer.CreatedUser, (int) Constants.Reputation.AnswerVotedDown);
                        Helper.ChangeReputation(vote.Voter, (int) Constants.Reputation.VoteDownAnswer);
                    }
                    else
                    {
                        //投票禁止
                        _log.Error(
                            string.Format("An error has occured at {0} action. ", MethodBase.GetCurrentMethod().Name) +
                            " This user already voted this post.");

                        return View("Error",
                            new HandleErrorInfo(new Exception(Messages.ANSWER_ALREADY_VOTED), "Question", "Vote"));
                    }
                    if (Helper.GetUserInfo(vote.Voter).Reputation < (int) Constants.Privileges.VoteDown)
                    {
                        var result = new ValidationResult();
                        result.Errors.Add(new ValidationFailure("Reps", Messages.NOT_ENOUGH_PERMISSION));
                        return null;
                    }
                    _db.SaveChanges();
                    _log.Debug("End action " + MethodBase.GetCurrentMethod().Name);
                    return RedirectToAction("Details", new {id = answer.QuestionID});
                }
            }
            catch (InvalidOperationException exception)
            {
                _log.Error(
                    string.Format("An {0} exception has occured at {1} action. ", exception.GetType(),
                        MethodBase.GetCurrentMethod().Name) + exception.Message, exception);
                return View("Error", new HandleErrorInfo(exception, "Question", "Vote"));
            }
            catch (ArgumentNullException exception)
            {
                _log.Error(
                    string.Format("An {0} exception has occured at {1} action. ", exception.GetType(),
                        MethodBase.GetCurrentMethod().Name) + exception.Message, exception);
                return View("Error", new HandleErrorInfo(exception, "Question", "Vote"));
            }
            catch (Exception exception)
            {
                _log.Fatal(
                    string.Format("A fatal exception has occured at {0} action ", MethodBase.GetCurrentMethod().Name) +
                    exception.Message, exception);
                throw;
            }
        }

        /// <summary>
        /// 新しい論評を投稿します。
        /// </summary>
        /// <param name="comment">Commentモデル</param>
        /// <returns>Detailsビュー</returns>
        [HttpPost]
        public ActionResult Comment(Comment comment)
        {
            _log.Debug("Start action " + MethodBase.GetCurrentMethod().Name +
                       string.Format(". From userid {0} to questionid {1} and answerid {2}", comment.CreatedUser,
                           comment.QuestionID, comment.AnswerID));

            try
            {
                var validator = new CustomValidators.CommentValidator();
                ValidationResult result = validator.Validate(comment);
                TempData.Add("ValidationFailures", result.Errors);
                _log.Debug(string.Format("Validating comment content : {0}", comment.Content));
                if (!result.IsValid)
                {
                    _log.Error(
                        string.Format("An error has occured at {0} action. ", MethodBase.GetCurrentMethod().Name) +
                        " Comment is not valid.");

                    return null;
                }
                _db.Comments.Add(comment);
                if (Helper.GetUserInfo(comment.CreatedUser).Reputation < (int) Constants.Privileges.Comment)
                {
                    result.Errors.Add(new ValidationFailure("Reps", Messages.NOT_ENOUGH_PERMISSION));
                    
                    TempData.Add("ValidationFailures", result.Errors);
                    
                    return null;
                }
                _db.SaveChanges();
                _log.Debug("End action " + MethodBase.GetCurrentMethod().Name);
                return RedirectToAction("Details", new {id = comment.QuestionID});
            }
            catch (InvalidOperationException exception)
            {
                _log.Error(
                    string.Format("An {0} exception has occured at {1} action. ", exception.GetType(),
                        MethodBase.GetCurrentMethod().Name) + exception.Message, exception);
                return View("Error", new HandleErrorInfo(exception, "Question", "Comment"));
            }
            catch (ArgumentNullException exception)
            {
                _log.Error(
                    string.Format("An {0} exception has occured at {1} action. ", exception.GetType(),
                        MethodBase.GetCurrentMethod().Name) + exception.Message, exception);
                return View("Error", new HandleErrorInfo(exception, "Question", "Comment"));
            }
            catch (Exception exception)
            {
                _log.Fatal(
                    string.Format("A fatal exception has occured at {0} action ", MethodBase.GetCurrentMethod().Name) +
                    exception.Message, exception);
                throw;
            }
        }

        /// <summary>
        /// 現在な回答を編集します。
        /// </summary>
        /// <param name="editAnswer">EditAnswerモデル</param>
        /// <returns>Detailsビュー</returns>
        [HttpPost]
        public ActionResult EditAnswer(EditAnswerModel editAnswer)
        {
            _log.Debug("Start action " + MethodBase.GetCurrentMethod().Name +
                       string.Format(". From userid {0} to answerid {1}", editAnswer.ModifiedUser, editAnswer.AnswerID));

            try
            {
                var test = Helper.Html2Text(editAnswer.Content);
                Answer ans = _db.Answers.Find(editAnswer.AnswerID);
                if (ans == null)
                {
                    return View("Error",
                        new HandleErrorInfo(new Exception(Messages.ANSWERID_DOES_NOT_EXIST), "Question", "Vote"));
                }
                ans.Content = editAnswer.Content;
                ans.EditReason = editAnswer.EditReason;
                ans.ModifiedUser = editAnswer.ModifiedUser;
                ans.LastModifiedDate = DateTime.Now;
                _log.Debug(string.Format("Validating answer content : {0}", ans.Content));
                var validator = new CustomValidators.AnswerValidator();
                ValidationResult result = validator.Validate(ans);
                TempData.Add("ValidationFailures", result.Errors);
                if (!result.IsValid)
                {
                    _log.Error(string.Format("An error occured at : {0}. Answer content is not valid.",
                        MethodBase.GetCurrentMethod().Name));
                    return RedirectToAction("Details", new {id = ans.QuestionID});
                }
                if (WebSecurity.CurrentUserId == editAnswer.ModifiedUser)
                {
                    
                }
                else if (Helper.GetUserInfo(editAnswer.ModifiedUser).Reputation < (int) Constants.Privileges.EditPost || Helper.GetUserInfo(editAnswer.ModifiedUser).BanDuration > 0)
                {
                    result.Errors.Add(new ValidationFailure("Reps", Messages.NOT_ENOUGH_PERMISSION));
                    TempData.Add("ValidationFailures", result.Errors);
                    return null;
                }
                _db.SaveChanges();
                _log.Debug("End action " + MethodBase.GetCurrentMethod().Name);

                return RedirectToAction("Details", new {id = ans.QuestionID});
            }
            catch (InvalidOperationException exception)
            {
                _log.Error(
                    string.Format("An {0} exception has occured at {1} action. ", exception.GetType(),
                        MethodBase.GetCurrentMethod().Name) + exception.Message, exception);
                return View("Error", new HandleErrorInfo(exception, "Question", "EditAnswer"));
            }
            catch (ArgumentNullException exception)
            {
                _log.Error(
                    string.Format("An {0} exception has occured at {1} action. ", exception.GetType(),
                        MethodBase.GetCurrentMethod().Name) + exception.Message, exception);
                return View("Error", new HandleErrorInfo(exception, "Question", "EditAnswer"));
            }
            catch (Exception exception)
            {
                _log.Fatal(
                    string.Format("A fatal exception has occured at {0} action ", MethodBase.GetCurrentMethod().Name) +
                    exception.Message, exception);
                throw;
            }
        }

        /// <summary>
        /// 回答を編集するための現実のビュー
        /// </summary>
        /// <param name="answerId">回答のID</param>
        /// <returns>_EditAnswer部分ビュー</returns>
        public ActionResult EditPartial(int answerId)
        {
            _log.Debug("Start action " + MethodBase.GetCurrentMethod().Name +
                       string.Format(". Edit answerid {0}", answerId));

            try
            {
                Answer answer = _db.Answers.FirstOrDefault(a => a.AnswerID == answerId);
                if (answer == null)
                {
                    _log.Error(string.Format("Answerid {0} doesnt exist.", answerId));
                    return View("Error",
                        new HandleErrorInfo(new Exception(Messages.ANSWERID_DOES_NOT_EXIST), "Question", "EditPartial"));
                }

                var editAnswer = new EditAnswerModel
            {
                AnswerID = answer.AnswerID,
                Content = answer.Content,
                EditReason = "",
                ModifiedUser = WebSecurity.CurrentUserId
            };
                _log.Debug("End action " + MethodBase.GetCurrentMethod().Name);
                return PartialView("_EditAnswer", editAnswer);
            }
            catch (InvalidOperationException exception)
            {
                _log.Error(
                    string.Format("An {0} exception has occured at {1} action. ", exception.GetType(),
                        MethodBase.GetCurrentMethod().Name) + exception.Message, exception);
                return View("Error", new HandleErrorInfo(exception, "Question", "EditPartial"));
            }
            catch (ArgumentNullException exception)
            {
                _log.Error(
                    string.Format("An {0} exception has occured at {1} action. ", exception.GetType(),
                        MethodBase.GetCurrentMethod().Name) + exception.Message, exception);
                return View("Error", new HandleErrorInfo(exception, "Question", "EditPartial"));
            }
            catch (Exception exception)
            {
                _log.Fatal(
                    string.Format("A fatal exception has occured at {0} action ", MethodBase.GetCurrentMethod().Name) +
                    exception.Message, exception);
                throw;
            }
        }

        /// <summary>
        /// 回答を「問題解決できる」と投票します
        /// </summary>
        /// <param name="userId">この作用を実現したユーザー</param>
        /// <param name="answerId">回答のID</param>
        /// <returns>Detailsビュー</returns>
        public ActionResult AcceptAnswer(int userId, int answerId)
        {
            _log.Debug("Start action " + MethodBase.GetCurrentMethod().Name +
                       string.Format(". userid {0} accept answerid {1}", userId, answerId));

            try
            {
                Answer answer = _db.Answers.FirstOrDefault(a => a.AnswerID == answerId);
                if (answer == null || answer.IsDeleted == true || answer.IsSpam == true)
                {
                    _log.Error(string.Format("Answerid {0} doesnt exist.", answerId));
                    return View("Error",
                        new HandleErrorInfo(new Exception(Messages.ANSWERID_DOES_NOT_EXIST), "Question", "Comment"));
                }

                Helper.ChangeReputation(userId, (int) Constants.Reputation.AcceptAnswer);

                answer.IsAccepted = true;
                _log.Debug(
                    string.Format(
                        "Change reputation of question owner and answer owner . Set answer IsAccepted to true."));
                Helper.ChangeReputation(answer.CreatedUser, (int) Constants.Reputation.AnswerAccepted);
                Bounty bounty = _db.Bounties.SingleOrDefault(b => b.QuestionID == answer.QuestionID);
                if (bounty != null)
                {
                    _log.Debug(string.Format("A bountyid {0} existed on this questionid {1}.", bounty.BountyID,
                        bounty.QuestionID));

                    Helper.ChangeReputation(_db.Users.Find(answer.CreatedUser).UserID, bounty.Amount);
                    _log.Debug(string.Format("Award bounty to the answer owner."));
                    var bt = _db.Bounties.SingleOrDefault(b => b.QuestionID == answer.QuestionID);
                    if (bt != null)
                    {
                        bt.LastModifiedDate = DateTime.Now;
                        bt.ModifiedUser = userId;
                        bt.AnswerID = answerId;
                        bt.Duration = 0;
                    }
                }
                _db.SaveChanges();
                _log.Debug("End action " + MethodBase.GetCurrentMethod().Name);
                return RedirectToAction("Details", new {id = answer.QuestionID});
            }
            catch (InvalidOperationException exception)
            {
                _log.Error(
                    string.Format("An {0} exception has occured at {1} action. ", exception.GetType(),
                        MethodBase.GetCurrentMethod().Name) + exception.Message, exception);
                return View("Error", new HandleErrorInfo(exception, "Question", "AcceptAnswer"));
            }
            catch (ArgumentNullException exception)
            {
                _log.Error(
                    string.Format("An {0} exception has occured at {1} action. ", exception.GetType(),
                        MethodBase.GetCurrentMethod().Name) + exception.Message, exception);
                return View("Error", new HandleErrorInfo(exception, "Question", "AcceptAnswer"));
            }
            catch (Exception exception)
            {
                _log.Fatal(
                    string.Format("A fatal exception has occured at {0} action ", MethodBase.GetCurrentMethod().Name) +
                    exception.Message, exception);
                throw;
            }
        }

        /// <summary>
        /// 現在な質問に報奨をかけます。
        /// </summary>
        /// <param name="bounty">Bountyモデル</param>
        /// <returns>Detailsビュー</returns>
        [HttpPost]
        public ActionResult SetBounty(Bounty bounty)
        {
            _log.Debug("Start action " + MethodBase.GetCurrentMethod().Name +
                       string.Format(". userid {0} set bounty amount {1} on questionid {2}", bounty.CreatedUser,
                           bounty.Amount, bounty.QuestionID));

            try
            {
                var validator = new CustomValidators.BountyValidator();
                ValidationResult result = validator.Validate(bounty);
                TempData.Add("ValidationFailures", result.Errors);
                _log.Debug(string.Format("Validating bounty amount..."));
                if (result.IsValid)
                {
                    Helper.ChangeReputation(_db.Users.Find(bounty.CreatedUser).UserID, -bounty.Amount);
                    _db.Bounties.Add(bounty);
                    _db.SaveChanges();
                    Bounty singleOrDefault = _db.Bounties.SingleOrDefault(b => b.QuestionID == bounty.QuestionID);
                    if (singleOrDefault != null)
                        _db.Questions.Find(bounty.QuestionID).BountyID =
                            singleOrDefault.BountyID;
                    if (Helper.GetUserInfo(bounty.CreatedUser).Reputation < (int) Constants.Privileges.SetBounty)
                    {
                        result.Errors.Add(new ValidationFailure("Amount", Messages.NOT_ENOUGH_PERMISSION));
                        TempData.Add("ValidationFailures", result.Errors);
                        return null;
                    }
                    _db.SaveChanges();
                    _log.Debug("End action " + MethodBase.GetCurrentMethod().Name);
                    return null;
                }
                _log.Error(string.Format("An error occured at {0}. Bounty amount is not valid",
                    MethodBase.GetCurrentMethod().Name));
                return null;
            }
            catch (InvalidOperationException exception)
            {
                _log.Error(
                    string.Format("An {0} exception has occured at {1} action. ", exception.GetType(),
                        MethodBase.GetCurrentMethod().Name) + exception.Message, exception);
                return View("Error", new HandleErrorInfo(exception, "Question", "SetBounty"));
            }
            catch (ArgumentNullException exception)
            {
                _log.Error(
                    string.Format("An {0} exception has occured at {1} action. ", exception.GetType(),
                        MethodBase.GetCurrentMethod().Name) + exception.Message, exception);
                return View("Error", new HandleErrorInfo(exception, "Question", "SetBounty"));
            }
            catch (Exception exception)
            {
                _log.Fatal(
                    string.Format("A fatal exception has occured at {0} action ", MethodBase.GetCurrentMethod().Name) +
                    exception.Message, exception);
                throw;
            }
        }

        /// <summary>
        /// New question ビューを作るため、データを 取り出します。
        /// </summary>
        /// <returns>Createビュー</returns>
        public ActionResult Create()
        {
            _log.Debug(string.Format("Start action {0}", MethodBase.GetCurrentMethod().Name));
            try
            {
                if (User.Identity.IsAuthenticated)
                {
                    _log.Debug(string.Format("User is authenticated."));
                    int currentUser = WebSecurity.CurrentUserId;
                    if (_db.Users.Find(currentUser).Reputation < (int) Constants.Privileges.Veteran && PostCount() > 4 ||
                        _db.Users.Find(currentUser).BanDuration != 0)
                    {
                        return View("Error");
                    }
                    //セッションにデータをほぞんします。
                    TempData["title"] = Session["title"] == null ? "" : Session["title"] + "";
                    TempData["content"] = Session["content"] == null ? "" : Session["content"] + "";
                    TempData["bounty"] = Session["bounty"] == null ? "" : Session["bounty"] + "";
                    TempData["tag"] = Session["tag"] == null ? "" : Session["tag"] + "";
                    Session["title"] = null;
                    Session["content"] = null;
                    Session["bounty"] = null;
                    Session["tag"] = null;
                    _log.Debug(string.Format("End Action {0}.", MethodBase.GetCurrentMethod().Name));
                    return View();
                }
                //まだログインしていません。
                _log.Error(string.Format("not login yet "));
                return RedirectToAction("Details");
            }
            catch (InvalidOperationException exception)
            {
                _log.Error(
                    string.Format("An {0} exception has occured at {1} action. ", exception.GetType(),
                        MethodBase.GetCurrentMethod().Name) + exception.Message, exception);
                return View("Error", new HandleErrorInfo(exception, "Question", "Create"));
            }
            catch (Exception exception)
            {
                _log.Fatal(
                    string.Format("A fatal exception has occured at {0} action ", MethodBase.GetCurrentMethod().Name) +
                    exception.Message, exception);
                throw;
            }
        }

        /// <summary>
        /// Edit ビューを作るため、データを 取り出します。
        /// </summary>
        /// <param name="questionId">Question id</param>
        /// <returns>Edit ビュー</returns>
        public ActionResult Edit(int questionId)
        {
            _log.Debug(string.Format("Start action {0}. Edit questionid {1}", MethodBase.GetCurrentMethod().Name,
                questionId));
            try
            {
                int currentUser = WebSecurity.CurrentUserId;
                var editQuestion = new EditQuestionModel();
                Question question = _db.Questions.First(b => b.QuestionID == questionId);
                var bounty = new Bounty();
                //質問は報奨があるかどうか確認します。
                if (question.BountyID.HasValue)
                {
                    _log.Debug(string.Format("Check if question already have bounty."));
                    bounty = _db.Bounties.First(b => b.QuestionID == questionId);
                }
                else
                {
                    bounty = null;
                }
                editQuestion.Question = question;
                editQuestion.Bounty = bounty;

                if (currentUser == question.CreatedUser && _db.Users.Find(currentUser).BanDuration == 0)
                {
                    _log.Debug(string.Format("End Action {0}.", MethodBase.GetCurrentMethod().Name));
                    return View(editQuestion);
                }
                if (Helper.GetUserInfo(currentUser).Reputation >= (int) Constants.Privileges.EditPost &&
                    _db.Users.Find(currentUser).BanDuration == 0)
                {
                    _log.Debug(string.Format("End Action {0}.", MethodBase.GetCurrentMethod().Name));
                    return View(editQuestion);
                }
                return RedirectToAction("Details");
            }
            catch (InvalidOperationException exception)
            {
                _log.Error(
                    string.Format("An {0} exception has occured at {1} action. ", exception.GetType(),
                        MethodBase.GetCurrentMethod().Name) + exception.Message, exception);
                return View("Error", new HandleErrorInfo(exception, "Question", "Edit"));
            }
            catch (ArgumentNullException exception)
            {
                _log.Error(
                    string.Format("An {0} exception has occured at {1} action. ", exception.GetType(),
                        MethodBase.GetCurrentMethod().Name) + exception.Message, exception);
                return View("Error", new HandleErrorInfo(exception, "Question", "Edit"));
            }
            catch (Exception exception)
            {
                _log.Fatal(
                    string.Format("A fatal exception has occured at {0} action ", MethodBase.GetCurrentMethod().Name) +
                    exception.Message, exception);
                throw;
            }
        }

        /// <summary>
        /// 新しい質問を作成します。
        /// </summary>
        /// <returns>Detailsビュー</returns>
        [ValidateInput(false)]
        public ActionResult Newquestion()
        {
            _log.Debug(string.Format("Start action {0}", MethodBase.GetCurrentMethod().Name));
            try
            {
                int currentUser = WebSecurity.CurrentUserId;
                if (User.Identity.IsAuthenticated && _db.Users.Find(currentUser).BanDuration == 0)
                {
                    _log.Debug(string.Format("User logged in and not banned."));
                    if (_db.Users.Find(currentUser).Reputation < (int) Constants.Privileges.Veteran && PostCount() > 4 ||
                        _db.Users.Find(currentUser).BanDuration != 0)
                    {
                        _log.Error(string.Format("User has exceeded daily limit of 5 question per day."));
                        return View("Error",new HandleErrorInfo(new Exception(Messages.NOT_ENOUGH_PERMISSION), "Question", "Newquestion"));
                    }
                    string bt = (Request.Form["bounty"] + "").Trim();
                    string title = (Request.Form["title1"] + "").Trim();
                    string content = (Request.Form["content"] + "").Trim();

                    while (content.StartsWith("<p><br></p>"))
                        content = content.Substring(11);
                    while (content.StartsWith("<br>"))
                        content = content.Substring(4);
                    while (content.EndsWith("<p><br></p>"))
                        content = content.Substring(0, content.Length - 11);
                    while (content.EndsWith("<br>"))
                        content = content.Substring(0, content.Length - 4);
                    int bounty = 0;
                    try{
                        bounty = string.IsNullOrEmpty(bt) ? 0 : int.Parse(bt);
                    }catch{
                        TempData["error"] = "bounty must be a number" ;
                        _log.Error(string.Format("bounty must be a number"));
                        return RedirectToAction("Create");
                    }
                    
                    int currentUserBounty = _db.Users.Find(currentUser).Reputation;
                    string requesttag = (Request.Form["TagName"] + "").Trim().Replace(" ", "");
                    String[] tag = requesttag.Split(',').Select(a => a.Trim()).ToArray();
                    //セッションにデータをほぞんします。
                    Session["title"] = title;
                    Session["content"] = content;
                    if (bounty != 0)
                    {
                        Session["bounty"] = bounty;
                    }
                    else
                        Session["bounty"] = "";
                    Session["tag"] = Request.Form["TagName"];

                    //サーバ側の検証
                    tag = Array.FindAll(tag, IsNotEmpty);
                    if (title.Trim().Length < Constants.QUESTION_TITLE_MINIMUM ||
                        Helper.Html2Text(content).Trim().Length < Constants.QUESTION_CONTENT_MINIMUM ||
                        tag.Length == 0)
                    {
                        TempData["error"] = Messages.QUESTION_TITLE_CONTENT_TAG_VALIDATION;
                        _log.Error(
                            string.Format(
                                "Title must be at least 10 characters ,Content must be at least 30 characters and tag is required"));
                        return RedirectToAction("Create");
                    }
                    //タグは存在するかどうか確認します。
                    string notInDb = "";
                    foreach (string s in tag)
                    {
                        if (s.Trim() != "")
                        {
                        int tagExist = _db.Tags.Count(a => a.TagName.Equals(s.Trim()));
                            if (tagExist == 0)
                            {
                                notInDb += s + ", ";
                            }
                        }
                    }

                    if (notInDb != "" && _db.Users.Find(currentUser).Reputation < (int) Constants.Privileges.CreateTag)
                    {
                        TempData["error"] = QAS_Nekorama.TAGS + notInDb + Messages.TAG_NOT_EXIST;
                        _log.Error(string.Format("don't have permissions to create new tag"));
                        return RedirectToAction("Create");
                    }

                    int questionId = 0;
                    int bountyId = 0;

                    //get tagName for question
                    HashSet<string> temp = InsertNewTag(tag);
                    var terms = new string[temp.Count];
                    temp.CopyTo(terms);
                    if (terms.Length == 0)
                    {
                        TempData["title"] = Session["title"] == null ? "" : Session["title"] + "";
                        TempData["content"] = Session["content"] == null ? "" : Session["content"] + "";
                        TempData["bounty"] = Session["bounty"] == null ? "" : Session["bounty"] + "";
                        TempData["tag"] = Session["tag"] == null ? "" : Request.Form["TagName"] + "";
                        TempData["error"] = Messages.NEW_TAG_NOT_ALLOWED + Request.Form["TagName"] + " ";
                        _log.Error(string.Format("you don't have permission to create new tag"));
                        return RedirectToAction("Create");
                    }
                    TempData["error"] = null;

                    //InsertQuestionの関数呼び出し
                    questionId = InsertQuestion(title, content, terms);

                    //報奨を入れるための評価点をチェックします。
                    if (questionId != 0 && bounty > 0 && currentUserBounty >= (int) Constants.Privileges.SetBounty &&
                        currentUserBounty > bounty)
                    {
                        _log.Debug(string.Format("Can create new bounty."));
                        bountyId = InsertBounty(bounty, questionId);
                        if (bountyId != 0)
                        {
                            User user = _db.Users.First(b => b.UserID == currentUser);
                            user.Reputation = user.Reputation - bounty;
                            _db.Questions.Find(questionId).BountyID = bountyId;
                            try
                            {
                                _db.SaveChanges();
                                //成功
                                _log.Debug(string.Format("Can create new bounty."));
                                return RedirectToAction("Details", new {id = questionId});
                            }
                            catch (Exception exception)
                            {
                                //失敗
                                _log.Fatal(
                                    string.Format("A fatal exception has occured at {0} action ",
                                        MethodBase.GetCurrentMethod().Name) + exception.Message, exception);
                                return View("Error",
                                    new HandleErrorInfo(new Exception(Messages.ERROR_DATABASE), "Question",
                                        "Newquestion"));
                            }
                        }
                    }
                    else if (questionId != 0 && currentUserBounty < bounty)
                    {
                        Session["title"] = null;
                        Session["content"] = null;
                        Session["bounty"] = null;
                        Session["tag"] = null;
                        _log.Debug(string.Format("Can create question"));
                        return RedirectToAction("Details", new {id = questionId});
                    }
                    else if (questionId != 0 && bounty == 0)
                    {
                        //報奨がないの質問
                        Session["title"] = null;
                        Session["content"] = null;
                        Session["bounty"] = null;
                        Session["tag"] = null;
                        _log.Debug(string.Format("Can create question without bounty."));
                        return RedirectToAction("Details", new {id = questionId});
                    }
                    else if (questionId == 0)
                    {
                        //失敗
                        TempData["error"] = null;
                        Session["bounty"] = null;
                        return View("Error",
                            new HandleErrorInfo(new Exception(Messages.ERROR_DATABASE), "Question", "Newquestion"));
                    }
                }
                else
                {
                    _log.Error(string.Format("don't have permission to create question."));
                    return View("Error",
                        new HandleErrorInfo(new Exception(Messages.NOT_ENOUGH_PERMISSION), "Question", "Newquestion"));
                }

                return RedirectToAction("Index");
            }
            catch (Exception exception)
            {
                _log.Fatal(
                    string.Format("A fatal exception has occured at {0} action ", MethodBase.GetCurrentMethod().Name) +
                    exception.Message, exception);
                throw;
            }
        }


        /// <summary>
        /// 空きストリングのチェック
        /// </summary>
        /// <param name="input">チェックされるストリング</param>
        /// <returns>空きストリングであるかどうか</returns>
        private static bool IsNotEmpty(string input)
        {
            return !input.Equals("");
        }

        /// <summary>
        /// 質問の編集
        /// </summary>
        /// <returns>Detailsビュー</returns>
        [ValidateInput(false)]
        public ActionResult Editquestion()
        {
            int currentUser = WebSecurity.CurrentUserId;
            if (User.Identity.IsAuthenticated && _db.Users.Find(currentUser).BanDuration == 0)
            {
                int questionId = int.Parse(Request.Form["qid"]);
                if (currentUser != _db.Questions.Find(questionId).CreatedUser && Helper.GetUserInfo(currentUser).Reputation < (int)Constants.Privileges.EditPost)
                {
                    TempData["error"] = Messages.NOT_ENOUGH_PERMISSION;
                    _log.Error(string.Format("Not enough rep."));
                    return RedirectToAction("Edit", new { questionId = questionId });
                }
                int bountyId = 0;
                //ビューからデータを取り出します。
                string title = (Request.Form["title"] + "").Trim();
                string content = (Request.Form["content"] + "").Trim();
                while (content.StartsWith("<p><br></p>"))
                    content = content.Substring(11);
                while (content.StartsWith("<br>"))
                    content = content.Substring(4);
                while (content.EndsWith("<p><br></p>"))
                    content = content.Substring(0, content.Length - 11);
                while (content.EndsWith("<br>"))
                    content = content.Substring(0, content.Length - 4);

                String[] tag = (Request.Form["tag"] + "").Trim().ToLower().Split(',');
                int returnValue;
                String bounty = Int32.TryParse(Request.Form["bounty"], out returnValue)
                ? returnValue.ToString()
                : null;

                int bountyNumber = 0;
                try {                     
                    bountyNumber = string.IsNullOrEmpty(bounty) ? 0 : int.Parse(bounty);
                }catch{
                    TempData["error"] = "bounty must be a number" ;
                        _log.Error(string.Format("bounty must be a number"));
                        return RedirectToAction("Edit", new {questionId = questionId});
                }
                int currentUserBounty = _db.Users.Find(currentUser).Reputation;
                Session["title1"] = title;
                Session["content"] = content;
                Session["tag"] = Request.Form["tag"];
                Session["bounty"] = bounty;

                //サーバ側の検証
                tag = Array.FindAll(tag, IsNotEmpty);
                if (title.Trim().Length < Constants.QUESTION_TITLE_MINIMUM ||
                    content.Trim().Length < Constants.QUESTION_CONTENT_MINIMUM || tag.Length == 0)
                {
                    TempData["error"] = Messages.QUESTION_TITLE_CONTENT_TAG_VALIDATION;
                    _log.Error(string.Format("Title, content and tag field are required."));
                    return RedirectToAction("Edit", new {questionId = questionId});
                }

                string notInDb = "";
                foreach (string s in tag)
                {
                    int tagExist = _db.Tags.Count(a => a.TagName.Equals(s.Trim()));
                    if (tagExist == 0)
                    {
                        notInDb += s + ", ";
                    }
                }
                if (notInDb != "" && _db.Users.Find(currentUser).Reputation < 3000)
                {
                    TempData["error"] = QAS_Nekorama.TAGS + notInDb + Messages.TAG_NOT_EXIST;
                    _log.Error(string.Format("don't have permissions to create new tag"));
                    return RedirectToAction("Edit", new { questionId = questionId });
                }

                if (_db.Questions.Find(questionId).BountyID.HasValue)
                {
                    bountyId = 0;
                }
                else if (bountyNumber != 0 && currentUserBounty >= (int)Constants.Privileges.SetBounty &&
                         currentUserBounty > bountyNumber)
                {
                    var newbounty = new Bounty
                    {
                        Amount = returnValue,
                        CreatedUser = currentUser,
                        Duration = Constants.BOUNTY_DURATION,
                        QuestionID = questionId,
                        CreatedDate = DateTime.Now
                    };
                    try
                    {
                        _db.Bounties.Add(newbounty);
                        _db.SaveChanges();
                        bountyId = newbounty.BountyID;
                    }
                    catch (Exception exception)
                    {
                        _log.Fatal(
                            string.Format("A fatal exception has occured at {0} action ",
                                MethodBase.GetCurrentMethod().Name) + exception.Message, exception);
                        bountyId = 0;
                        TempData["error"] = Messages.BOUNTY_FAILED;
                        return RedirectToAction("Edit");
                    }
                }
                else if (bounty != null && currentUserBounty < (int)Constants.Privileges.SetBounty)
                {
                    _log.Debug(string.Format("Can create new bounty."));
                    TempData["error"] = Messages.NOT_ENOUGH_PERMISSION;
                    return RedirectToAction("Edit");
                }

                if (bountyId != 0)
                {
                    User user = _db.Users.First(b => b.UserID == currentUser);
                    user.Reputation -= bountyNumber;
                    _db.Questions.Find(questionId).BountyID = bountyId;
                    try
                    {
                        _db.SaveChanges();
                        Session["title"] = null;
                        Session["content"] = null;
                        Session["bounty"] = null;
                        Session["tag"] = null;
                        _log.Debug(string.Format("Can create new bounty."));
                        return RedirectToAction("Details", new {id = questionId});
                    }
                    catch (Exception exception)
                    {
                        //失敗
                        _log.Fatal(
                            string.Format("A fatal exception has occured at {0} action ",
                                MethodBase.GetCurrentMethod().Name) + exception.Message, exception);
                        return View("Error",
                            new HandleErrorInfo(new Exception(Messages.ERROR_DATABASE), "Question", "Editquestion"));
                    }
                }

                HashSet<string> temp = InsertNewTag(tag);
                var terms = new string[temp.Count];
                temp.CopyTo(terms);
                if (terms.Length == 0)
                {
                    TempData["error"] = Messages.NEW_TAG_NOT_ALLOWED + Request.Form["TagName"];
                    _log.Error(string.Format("don't have permission to create new tag."));
                    return RedirectToAction("Edit");
                }
                TempData["error"] = null;
                if (UpdateQuestion(questionId, title, content, terms, bountyId))
                {
                    Session["title"] = null;
                    Session["content"] = null;
                    Session["bounty"] = null;
                    Session["tag"] = null;
                    _log.Debug(string.Format("Can edit question."));
                    return RedirectToAction("Details", new {id = questionId});
                }
                }
            else
            {
                _log.Error(string.Format("don't have permission to edit question."));
                return View("Error",
                    new HandleErrorInfo(new Exception(Messages.NOT_ENOUGH_PERMISSION), "Question", "Editquestion"));
            }
            return null;
        }

        /// <summary>
        /// 投稿した質問を数えます。
        /// </summary>
        /// <returns>投稿した質問の数</returns>
        private int PostCount()
        {
            int currentUser = WebSecurity.CurrentUserId;
            IQueryable<Question> b = _db.Questions.Where(c => c.CreatedUser == currentUser);
            int count = 0;
            foreach (Question question in b)
            {
                if (question.CreatedDate.Date == DateTime.Today.Date)
                {
                    count++;
                }
            }
            return count;
        }

        /// <summary>
        /// 変更の更新
        /// </summary>
        /// <param name="questionId">質問のＩＤ</param>
        /// <param name="title">質問タイトル</param>
        /// <param name="content">質問の内容</param>
        /// <param name="tags">タグリスト</param>
        /// <param name="bountyId">報奨のＩＤ</param>
        /// <returns>成功かどうか</returns>
        private bool UpdateQuestion(int questionId, string title, string content, string[] tags, int bountyId)
        {
            _log.Debug(
                string.Format(
                    "Update question action with {0} question's id,{1} question's title, {2} question's content,{3} tags, {4} bounty's id",
                    questionId, title, content, tags, bountyId));
            int currentUser = WebSecurity.CurrentUserId;
            Question question = _db.Questions.First(b => b.QuestionID == questionId);
            question.Title = title;
            question.Content = content;
            question.TagName1 = tags[0].Trim();
            question.ModifiedUser = currentUser;
            question.LastModifiedDate = DateTime.Now;

            question.TagName2 = tags.Length > 1 ? tags[1].Trim() : null;
            question.TagName3 = tags.Length > 2 ? tags[2].Trim() : null;
            question.TagName4 = tags.Length > 3 ? tags[3].Trim() : null;
            question.TagName5 = tags.Length > 4 ? tags[4].Trim() : null;

            if (bountyId != 0)
            {
                question.BountyID = bountyId;
            }

            try
            {
                _db.SaveChanges();
                return true;
            }
            catch (Exception exception)
            {
                _log.Fatal(
                    string.Format("A fatal exception has occured at {0} action ", MethodBase.GetCurrentMethod().Name) +
                    exception.Message, exception);
                return false;
            }
        }

        /// <summary>
        /// 報奨を入れます。
        /// </summary>
        /// <param name="bountyAmount">報奨量</param>
        /// <param name="questionId">質問のＩＤ</param>
        /// <returns>報奨のＩＤ</returns>
        private int InsertBounty(int bountyAmount, int questionId)
        {
            _log.Debug(string.Format("Insert bounty action with {0} amount of bounty,{1} question's id", bountyAmount,
                questionId));
            int currentUser = WebSecurity.CurrentUserId;
            var newBounty = new Bounty
            {
                Amount = bountyAmount,
                CreatedUser = currentUser,
                Duration = Constants.BOUNTY_DURATION,
                QuestionID = questionId,
                CreatedDate = DateTime.Now
            };
            try
            {
                _db.Bounties.Add(newBounty);
                _db.SaveChanges();
                int bountyId = newBounty.BountyID;
                return bountyId;
            }
            catch (Exception exception)
            {
                _log.Fatal(
                    string.Format("A fatal exception has occured at {0} action ", MethodBase.GetCurrentMethod().Name) +
                    exception.Message, exception);
                _db.Questions.Remove(_db.Questions.Find(questionId));
                _db.SaveChanges();
                return 0;
            }
        }
        /// <summary>
        /// 新しいタグを作成します。
        /// </summary>
        /// <param name="tags">タグリスト</param>
        /// <returns>タグのHashSet</returns>
        private HashSet<string> InsertNewTag(string[] tags)
        {
            _log.Debug(string.Format("Insert new tags action with {0} tags", tags));
            var legalTags = new HashSet<string>();
            int currentUser = WebSecurity.CurrentUserId;
            foreach (string s in tags)
            {
                int tagExist = _db.Tags.Count(a => a.TagName.Equals(s.Trim()));
                if (tagExist == 0 && s != "" &&
                    _db.Users.Find(currentUser).Reputation >= (int) Constants.Privileges.CreateTag)
                {
                    var newTag = new Tag
                {
                                     TagName = s.Trim().ToLower(),
                                     CreatedUser = currentUser,
                                     CreatedDate = DateTime.Now
                                 };
                    _db.Tags.Add(newTag);
                    legalTags.Add(s);
                }
                else if (tagExist != 0 && s != "")
                {
                    legalTags.Add(s);
                }
            }
            try
            {
                _db.SaveChanges();
                _log.Debug(string.Format("Can create new tag."));
            }
            catch (Exception exception)
            {
                _log.Fatal(
                    string.Format("A fatal exception has occured at {0} action ", MethodBase.GetCurrentMethod().Name) +
                    exception.Message, exception);
            }

            return legalTags;
        }

        /// <summary>
        /// データベースに新しい質問を入れます。
        /// </summary>
        /// <param name="title">質問タイトル</param>
        /// <param name="content">質問の内容</param>
        /// <param name="tags">タグリスト</param>
        /// <returns>質問のＩＤ</returns>
        private int InsertQuestion(string title, string content, string[] tags)
        {
            _log.Debug(string.Format(
                "Insert question action with {0} question's title,{1} question's content, {2} tags", title, content,
                tags));
            int currentUser = WebSecurity.CurrentUserId;
            if (content.Contains("background-color"))
            {
                content = content.Replace("background-color", "");
            }
            string tagName = "";
            if (tags.Length == 0)
            {
                tagName = " ";
            }
            else
            {
                tagName = tags[0];
            }
            var question = new Question
            {
                CreatedUser = currentUser,
                TagName1 = tagName,
                Title = title,
                IsDeleted = false,
                IsSpam = false,
                VoteCount = 0,
                Content = content,
                CreatedDate = DateTime.Now,
                LastModifiedDate = DateTime.Now,
                ModifiedUser = currentUser
            };
            question.TagName2 = tags.Length > 1 ? tags[1].Trim() : null;
            question.TagName3 = tags.Length > 2 ? tags[2].Trim() : null;
            question.TagName4 = tags.Length > 3 ? tags[3].Trim() : null;
            question.TagName5 = tags.Length > 4 ? tags[4].Trim() : null;

            _db.Questions.Add(question);
            try
            {
                _db.SaveChanges();
                _log.Debug(string.Format("Can create new question."));
                int questionId = question.QuestionID;
                return questionId;
            }
            catch (Exception exception)
            {
                _log.Fatal(
                    string.Format("A fatal exception has occured at {0} action ", MethodBase.GetCurrentMethod().Name) +
                    exception.Message, exception);
                return 0;
            }
        }

        /// <summary>
        /// オートコンプリートテキストボックスを表示するために、データベースからタグを取り出します。
        /// </summary>
        /// <returns>タグのセット</returns>
        [HttpGet]
        public JsonResult GetTags()
        {
            string[] allTag = (Request.QueryString["tags"] + "").Trim().Split(',');
            string tag = allTag[allTag.Length - 1].Trim();

            //var regex = new Regex(tag, RegexOptions.IgnoreCase);

            Regex r = new Regex("(?:[^a-z0-9 ]|(?<=['\"])s)", RegexOptions.IgnoreCase | RegexOptions.CultureInvariant | RegexOptions.Compiled);
            var regex = r.Replace(tag, String.Empty);

            var data = _db.Tags.Select(t => new {tagName = t.TagName}).Where(t => t.tagName.StartsWith(tag));
            var returnValue = new List<string>();
            foreach (var tags in data)
            {
                returnValue.Add(tags.tagName);
            }
            return Json(new {result = returnValue}, JsonRequestBehavior.AllowGet);
        }

        /// <summary>
        ///　ストリングはパターンに似ているかどうかチェックします。
        /// </summary>
        /// <param name="s">チェックされるストリング</param>
        /// <param name="pattern">パターン</param>
        /// <param name="options">パターンオプション</param>
        /// <returns></returns>
        public bool Like(string s, string pattern, RegexOptions options = RegexOptions.IgnoreCase)
        {
            return Regex.IsMatch(s, pattern, options);
        }
        /// <summary>
        /// 使用の後、エンティティのオブジェクトを削除します。
        /// </summary>
        /// <param name="disposing">削除するかどうか</param>
        protected override void Dispose(bool disposing)
        {
            _db.Dispose();
            base.Dispose(disposing);
        }
        /// <summary>
        /// アクションで処理されない例外を処理します。
        /// </summary>
        /// <param name="filterContext">例外のコンテキスト</param>
        protected override void OnException(ExceptionContext filterContext)
        {
            filterContext.Exception = new Exception("");
            filterContext.ExceptionHandled = true;
            filterContext.Result = View("Error", new HandleErrorInfo(filterContext.Exception, "Question", "Details"));
            
        }
    }
}